/*********************************************************************************
 *      Copyright:  (C) 2022 dairongan<2974932606@qq.com>
 *                  All rights reserved.
 *
 *       Filename:  spi_oled1.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(08/01/2022)
 *         Author:  dairongan <2974932606@qq.com>
 *      ChangeLog:  1, Release initial version on "08/01/2022 10:23:52 AM"
 *                 
 ********************************************************************************/

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/spi/spi.h>
#include <linux/mm.h>

#define OLED_NAME                                       "OLED"

#define OLED_CMD                                        0
#define OLED_DATA                                       1
#define OLED_SETPOS                                     3

static int                                                      dev_major;
unsigned char                                           *mmap_data;             // 用于做缓存的全局变量

int                                                             cs_gpio;                // 片选所使用的GPIO编号
int                                                             reset_gpio;             // 重置所使用的GPIO编号
int                                                             dc_gpio;                // 数据，命令选择线所使用的GPIO编号

struct oled_priv {
        struct cdev                                     cdev;
        struct class                                    *class;
        struct device                                   *device;
        struct spi_device                               *spi_dev;
};

static int oled_write_regs(struct oled_priv *priv, unsigned char *buf, int len)
{
        struct spi_message                      spi_mes;
        struct spi_transfer             spi_trans =
        {
                .tx_buf = buf,
                .len = len,
        };

        // 使能oled，片选拉低
        gpio_set_value(cs_gpio, 0);

        spi_message_init(&spi_mes);
        spi_message_add_tail(&spi_trans, &spi_mes);
        spi_sync(priv->spi_dev, &spi_mes);

        // 释放oled，片选拉高
        gpio_set_value(cs_gpio, 1);

        return 0;
}

static void oled_write_onereg(struct oled_priv *priv, unsigned char buf, unsigned short cmd)
{
        if(cmd)
        {
                gpio_set_value(dc_gpio, 1);
        }
        else
        {
                gpio_set_value(dc_gpio, 0);
        }

        oled_write_regs(priv, &buf, 1);
        gpio_set_value(dc_gpio, 1);
}

// 配置写入数据的起始位置
void oled_set_inital_position(struct oled_priv *priv, unsigned short x,unsigned short y)
{
        oled_write_onereg(priv, 0xb0+y, OLED_CMD);                              //设置行起始地址
        oled_write_onereg(priv, ((x&0xf0)>>4)|0x10, OLED_CMD);  //设置低列起始地址
        oled_write_onereg(priv, (x&0x0f)|0x01, OLED_CMD);               //设置高列起始地址
}

// 将 data 中的数据刷新到屏幕上
// 这里的 data 在之后的 write 和 ioctrl 中有所不同
// write 刷新的数据是 copy_from_user
// ioctrl 刷新的数据是 mmap 在应用层 memcpy 获取的
static void oled_refresh(struct oled_priv *priv, unsigned char *data)
{
        int     n = 0;
        int     x_axis = 0;
        int     y_axis = 0;

        for(y_axis=0; y_axis<8; y_axis++)
        {
                oled_set_inital_position(priv, 0, y_axis);
                for(x_axis=0; x_axis<128; x_axis++)
                {
                        oled_write_onereg(priv, *(data+n), OLED_DATA);
                        n++;
                }
        }
}

// 清屏函数
static void oled_clear(struct oled_priv *priv)
{
        int     n = 0;
        int     x_axis = 0;
        int     y_axis = 0;

        for(y_axis=0; y_axis<8; y_axis++)
        {
                oled_set_inital_position(priv, 0, y_axis);
                for(x_axis=0; x_axis<128; x_axis++)
                {
                        oled_write_onereg(priv, 0x0, OLED_DATA);
                        n++;
                }
        }
}


static int oled_open(struct inode *inode, struct file *filp)
{
        struct oled_priv *priv = container_of(inode->i_cdev, struct oled_priv, cdev);
    filp->private_data = priv;

        return 0;
}

static ssize_t oled_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
        int ret = 0;

        ret = copy_to_user(buf, mmap_data, 1024);
        if(ret < 0)
        {
                printk("oled_read fail\r\n");
                return -EFAULT;
        }
        return 0;
}

static ssize_t oled_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *off)
{
        struct                  oled_priv *priv = filp->private_data;
        unsigned char           databuf[1024];
        int                                     ret;

        ret = copy_from_user(databuf, buf, 1024);
        if(ret < 0)
        {
                printk("oled_write fail\r\n");
                return -EFAULT;
        }

        oled_clear(priv);
        oled_refresh(priv, databuf);

        return 0;
}

static int oled_mmap(struct file *filp, struct vm_area_struct *vma)
{
        vma->vm_page_prot = pgprot_cached(vma->vm_page_prot);

    if (remap_pfn_range(vma, vma->vm_start, virt_to_phys(mmap_data) >> PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot))
        {
                return -ENOBUFS;
        }
    return 0;
}


static long oled_ioctrl(struct file *filp, unsigned int cmd, unsigned long arg)
{
        int ret=0;
        char __user *arg_buf=(char __user*)&arg;
        unsigned char arg_data=0;
        struct oled_priv        *priv = filp->private_data;

        //从应用层传过来的数据都是不可信的,不安全的,copy_from_user可以对数据合法性检测
        ret=copy_from_user(&arg_data,arg_buf,sizeof(arg_data));
        if(ret < 0)
        {
                printk("ioctrl input invalid_argument !\r\n");
                return -EFAULT;
        }

        switch(cmd)
        {
                case OLED_DATA:
                        oled_write_onereg(priv,arg_data,OLED_DATA);
                        break;
                case OLED_CMD:
                        oled_write_onereg(priv,arg_data,OLED_CMD);
                        break;
                case OLED_SETPOS:
                        oled_set_inital_position(priv,arg/10,arg%10);
                        break;

                default:
                        break;
        }

        return 0;
}

static int oled_release(struct inode *inode, struct file *filp)
{
        return 0;
}

static const struct file_operations oled_ops = {
        .owner                  = THIS_MODULE,
        .open                   = oled_open,
        .read                   = oled_read,
        .write                  = oled_write,
        .mmap                   = oled_mmap,
        .unlocked_ioctl = oled_ioctrl,
        .release                = oled_release,
};

void oled_reginit(struct oled_priv *priv)
{
        gpio_set_value(reset_gpio, 1);
        mdelay(100);
        gpio_set_value(reset_gpio, 0);
        mdelay(200);
        gpio_set_value(reset_gpio, 1);

        oled_write_onereg(priv, 0xAE, OLED_CMD);
        oled_write_onereg(priv, 0xAE, OLED_CMD);
        oled_write_onereg(priv, 0x00, OLED_CMD);
        oled_write_onereg(priv, 0x10, OLED_CMD);
        oled_write_onereg(priv, 0x40, OLED_CMD);
        oled_write_onereg(priv, 0x81, OLED_CMD);
        oled_write_onereg(priv, 0xCF, OLED_CMD);
        oled_write_onereg(priv, 0xA1, OLED_CMD);
        oled_write_onereg(priv, 0xC8, OLED_CMD);
        oled_write_onereg(priv, 0xA6, OLED_CMD);
        oled_write_onereg(priv, 0xA8, OLED_CMD);
        oled_write_onereg(priv, 0x3f, OLED_CMD);
        oled_write_onereg(priv, 0xD3, OLED_CMD);
        oled_write_onereg(priv, 0x00, OLED_CMD);
        oled_write_onereg(priv, 0xd5, OLED_CMD);
        oled_write_onereg(priv, 0x80, OLED_CMD);
        oled_write_onereg(priv, 0xD9, OLED_CMD);
        oled_write_onereg(priv, 0xF1, OLED_CMD);
        oled_write_onereg(priv, 0xDA, OLED_CMD);
        oled_write_onereg(priv, 0x12, OLED_CMD);
        oled_write_onereg(priv, 0xDB, OLED_CMD);
        oled_write_onereg(priv, 0x40, OLED_CMD);
        oled_write_onereg(priv, 0x20, OLED_CMD);
        oled_write_onereg(priv, 0x02, OLED_CMD);
        oled_write_onereg(priv, 0x8D, OLED_CMD);
        oled_write_onereg(priv, 0x14, OLED_CMD);
        oled_write_onereg(priv, 0xA4, OLED_CMD);
        oled_write_onereg(priv, 0xA6, OLED_CMD);
        oled_write_onereg(priv, 0xAF, OLED_CMD);

        oled_clear(priv);
}

static int oled_probe(struct spi_device *spi_dev)
{
        struct oled_priv        *priv;
    dev_t                   devno;
    int                     ret = 0;
        struct device_node              *np = of_find_node_by_path("/soc/bus@2000000/spba-bus@2000000/spi@2008000");     // 当前设备节点

        cs_gpio = of_get_named_gpio(np, "cs-gpio", 0);
        if(cs_gpio < 0)
        {
                printk("can't get cs-gpio");
                return -EINVAL;
        }
        ret = gpio_request(cs_gpio, "cs-gpio");
        ret = gpio_direction_output(cs_gpio, 1);
        if(ret < 0)
        {
                printk("can't set cs-gpio!\r\n");
        }

        reset_gpio = of_get_named_gpio(np, "reset-gpio", 0);
        if(reset_gpio < 0)
        {
                printk("can't get reset-gpio");
                return -EINVAL;
        }
        ret = gpio_request(reset_gpio, "reset-gpio");
        ret = gpio_direction_output(reset_gpio, 1);
        if(ret < 0)
        {
                printk("can't set res-gpio!\r\n");
        }


        dc_gpio = of_get_named_gpio(np, "dc-gpio", 0);
        if(dc_gpio < 0)
        {
                printk("can't get reset-gpio");
                return -EINVAL;
        }
        ret = gpio_request(dc_gpio, "dc-gpio");
        ret = gpio_direction_output(dc_gpio, 1);
        if(ret < 0)
        {
                printk("can't set dc-gpio!\r\n");
        }

        printk("%d, %d, %d\n", cs_gpio, reset_gpio, dc_gpio);

    priv = devm_kzalloc(&spi_dev->dev, sizeof(struct oled_priv), GFP_KERNEL);
    if (!priv)
    {
        return -ENOMEM;
    }

    // 1）分配主次设备号
    // 动态分配主次设备号
    ret = alloc_chrdev_region(&devno, 0, 1, OLED_NAME);
    dev_major = MAJOR(devno);
    if (ret < 0)
    {
        dev_err(&spi_dev->dev, "major can't be allocated\n");
        return ret;
    }

    // 2）分配cdev结构体
    cdev_init(&priv->cdev, &oled_ops);
    priv->cdev.owner = THIS_MODULE;

    ret = cdev_add(&priv->cdev, devno , 1);
    if( ret < 0)
    {
        dev_err(&spi_dev->dev, "struture cdev can't be allocated\n");
        goto undo_major;
    }

    // 3）创建类，实现自动创建设备节点
    priv->class = class_create(THIS_MODULE, OLED_NAME);
    if( IS_ERR(priv->class) )
    {
        dev_err(&spi_dev->dev, "fail to create class\n");
        ret = -ENOMEM;
        goto undo_cdev;
    }

    // 4）创建设备
    devno = MKDEV(dev_major, 0);
    priv->device = device_create(priv->class, NULL, devno, NULL, OLED_NAME);
    if( IS_ERR(priv->device) )
    {
        dev_err(&spi_dev->dev, "fail to create device\n");
        ret = -ENOMEM;
        goto undo_class;
    }

    // 保存私有数据
        spi_dev->mode = SPI_MODE_0;
        spi_setup(spi_dev);
        priv->spi_dev = spi_dev;

        spi_set_drvdata(spi_dev, priv);

        oled_reginit(priv);

    dev_info(&spi_dev->dev, "oled driver probe okay.\n");

    return 0;

undo_class:
    class_destroy(priv->class);

undo_cdev:
    cdev_del(&priv->cdev);

undo_major:
    unregister_chrdev_region(devno, 1);
        return ret;
}

static int oled_remove(struct spi_device *spi_dev)
{
        struct oled_priv *priv = spi_get_drvdata(spi_dev);
    dev_t devno = MKDEV(dev_major, 0);

        kfree(mmap_data);

    device_destroy(priv->class, devno);
    class_destroy(priv->class);
    cdev_del(&priv->cdev);
    unregister_chrdev_region(MKDEV(dev_major, 0), 1);

        gpio_free(cs_gpio);
        gpio_free(reset_gpio);
        gpio_free(dc_gpio);

    printk("success to remove driver[major=%d]!\n", dev_major);
    return 0;
}

static const struct spi_device_id oled_id[] = {
        {"oled_spi", 0},
        {}
};

static const struct of_device_id oled_of_match[] = {
        { .compatible = "oled_spi" },
        { /* Sentinel */ }
};

static struct spi_driver oled_driver = {
        .probe = oled_probe,
        .remove = oled_remove,
        .driver = {
                        .owner = THIS_MODULE,
                        .name = "oled",
                        .of_match_table = oled_of_match,
                   },
        .id_table = oled_id,
};

static int __init oled_init(void)
{
        mmap_data = kmalloc(1024, GFP_KERNEL);
        return spi_register_driver(&oled_driver);
}

static void __exit oled_exit(void)
{
        spi_unregister_driver(&oled_driver);
}

module_init(oled_init);
module_exit(oled_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("dairongan");
MODULE_DESCRIPTION("spi oled driver on igkboard");

